Python2 for beginners (P4B)

Luca Ferroni

http://www.befair.it
**Software Libero per i territori**

CAPITOLO 1: base

Installazione su Windows

  • Python con Anaconda (v. sito ufficiale di python), per ottenere:
    • l'interprete python e l'installatore di pacchetti pip
    • la shell interattiva IPython
  • Un editor di testo, consigliati:
    • Notepad++
    • Eclipse
    • PyCharm
    • Atom
    • Visual Code

Python's mantras

WARNING

  • Python non si riferisce al serpente
  • Ma al Monthy Python Flying Circus

E ora... "The hello tour!"

Il mio primo codice python


In [1]:
# This is hello_who.py

def hello(who):
    print("Hello {}!".format(who))

if __name__ == "__main__":
    hello("mamma")


Hello mamma!

IPython compagno di sviluppo

$ ipython
Python 2.7.12 (default, Jun 28 2016, 08:31:05) Type "copyright", "credits" or "license" for more information. IPython 5.0.0 -- An enhanced Interactive Python. ? -> Introduction and overview of IPython's features. %quickref -> Quick reference. help -> Python's own help system. object? -> Details about 'object', use 'object??' for extra details. In [1]: %run hello_who.py Hello mamma! In [2]: !cat src/hello_who.py def hello(who): print("Hello {}!".format(who)) if __name__ == "__main__": hello("mamma")

Tip: scopri lo Zen di Python PEP 20


In [2]:
# This is hello_who.py                # <-- i commenti iniziano con `#`
                                      #     possono essere all'inizio o a fine riga
def hello(who):                       # <-- la funzione di definisce con `def`
    print("Hello {}!".format(who))    # <-- la stampa con `print` e le stringhe con `format`

if __name__ == "__main__":            # <-- [verifica di esecuzione e non di import](https://docs.python.org/2/library/__main__.html)
    hello("mamma")                    # <-- invoco la funzione con il valore


Hello mamma!

La leggibilità conta

  • Le linee guida di stile sono nel PEP 8
    • ogni azienda può averne di differenti

Le classiche sono:

  • Indentazione con 4 spazi, e non <TAB>: settate il vostro editor!
  • Lunghezza della riga <= 79 caratteri
  • Spazi intorno agli operatori

Le convenzioni per le docstring sono descritte nel PEP 257 in particolare:

  • Se monolinea scrivere tutto su una riga
  • Se multilinea il separatore finale (""") deve essere su una linea separata.

Mie convenzioni:

  • classi CamelCase e funzioni + variabili minuscole con _
  • il codice è scritto in inglese, in particolare i nomi delle variabili

I tipi di dato

  • numeri
  • stringhe
  • tuple: (1, 2 "a", "prova")
  • set: { 2, 4, 6,"a", 123 }
  • liste: [1,2,3,10,"a", -12.333]
  • dizionari: { "nome": "Luca", "cognome": "Ferroni"} -> è una tabella chiave:valore

LISTE: Operazioni e metodi


In [43]:
# creazione
l = [1,2,3,10,"a", -12.333, 1024, 768, "pippo"]

# concatenazione
l += ["la", "concatenazione", "della", "lista"]

# aggiunta elementi in fondo
l.append(32)
l.append(3)
print(u"la lista è {}".format(l))
l.remove(3)  # rimuove la prima occorrenza
print(u"la lista è {}".format(l))
i = l.index(10)  # restituisce l'indice della prima occorrenza del valore 10
print(u"l'indice di 10 è {}".format(i))
print(u"il valore all'indice 3 è {}".format(l[3]))
print(u"** vediamo come funziona lo SLICING delle liste")
print(u"Ecco i primi 3 valori della lista {}".format(l[:3]))
print(u"e poi i valori dal 3o al penultimo {}".format(l[3:-1]))
print(u"e poi i valori dal 3o al penultimo, ma ogni 2 {}".format(l[3:-1:2]))

print("\n***FUNZIONI RANGE e XRANGE***\n")
l2 = range(1000)  # questi sono i primi 1000 valori da 0 a 999
print(u"ecco la lista ogni 50 elementi di n <=1000: {}".format(l2[::50]))

# LA FUNZIONE xrange è comoda per ottenere un oggetto tipo (ma non = a ) un generatore 
# da cui i numeri vengono appunto generati al momento dell'accesso all'elemento stesso
# della sequenza

# Il codice di prima dà errore
try:
    l2 = xrange(1000)  # questi sono i primi 1000 valori da 0 a 999 ma senza occupare RAM
    print(u"ecco la lista ogni 50 elementi di n <= 1000: {}".format(l2[::50]))
except Exception as e:
    print("ECCEZIONE {}: {}".format(type(e), e))

# Il codice che funziona con lo slice valuta xrange in una lista quindi
# risulta inutile
l2 = list(xrange(1000))  # questi sono i primi 1000 valori da 0 a 999 ma senza occupare RAM
print(u"ecco la lista ogni 50 elementi di n <= 1000: {}\n".format(l2[::50]))

## ma si può fare direttamente con range o xrange!
print(u"[OK] lista ogni 50 elementi <= 1000: {}".format(range(0,1000,50)))


la lista è [1, 2, 3, 10, 'a', -12.333, 1024, 768, 'pippo', 'la', 'concatenazione', 'della', 'lista', 32, 3]
la lista è [1, 2, 10, 'a', -12.333, 1024, 768, 'pippo', 'la', 'concatenazione', 'della', 'lista', 32, 3]
l'indice di 10 è 2
il valore all'indice 3 è a
** vediamo come funziona lo SLICING delle liste
Ecco i primi 3 valori della lista [1, 2, 10]
e poi i valori dal 3o al penultimo ['a', -12.333, 1024, 768, 'pippo', 'la', 'concatenazione', 'della', 'lista', 32]
e poi i valori dal 3o al penultimo, ma ogni 2 ['a', 1024, 'pippo', 'concatenazione', 'lista']

***FUNZIONI RANGE e XRANGE***

ecco la lista ogni 50 elementi di n <=1000: [0, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950]
ECCEZIONE <type 'exceptions.TypeError'>: sequence index must be integer, not 'slice'
ecco la lista ogni 50 elementi di n <= 1000: [0, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950]

[OK] lista ogni 50 elementi <= 1000: [0, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950]

Iterazione nelle liste e cicli for su indice


In [44]:
print("***PER FARE UN CICLO FOR CON INDICE INCREMENTALE SI USA XRANGE!")

for el in xrange(1,21):
    print("numero {}".format(el))
    
print("***PER NUMERARE GLI ELEMENTI DI UNA LISTA SI USA ENUMERATE!")

for i, el in enumerate(l, start=10):  # numero partendo da 10, se start non specificato parto da 0
    print("Il contenuto {} si trova all'indice {}".format(el, i))


***PER FARE UN CICLO FOR CON INDICE INCREMENTALE SI USA XRANGE!
numero 1
numero 2
numero 3
numero 4
numero 5
numero 6
numero 7
numero 8
numero 9
numero 10
numero 11
numero 12
numero 13
numero 14
numero 15
numero 16
numero 17
numero 18
numero 19
numero 20
***PER NUMERARE GLI ELEMENTI DI UNA LISTA SI USA ENUMERATE!
Il contenuto 1 si trova all'indice 10
Il contenuto 2 si trova all'indice 11
Il contenuto 10 si trova all'indice 12
Il contenuto a si trova all'indice 13
Il contenuto -12.333 si trova all'indice 14
Il contenuto 1024 si trova all'indice 15
Il contenuto 768 si trova all'indice 16
Il contenuto pippo si trova all'indice 17
Il contenuto la si trova all'indice 18
Il contenuto concatenazione si trova all'indice 19
Il contenuto della si trova all'indice 20
Il contenuto lista si trova all'indice 21
Il contenuto 32 si trova all'indice 22
Il contenuto 3 si trova all'indice 23

DIZIONARI: Operazioni e metodi


In [45]:
# definizione
d = {"nome": "Luca", "cognome": "Ferroni", "classe": 1980}

# aggiornamento
d.update({
    "professioni" : ["docente", "lavoratore autonomo"]
})

# recupero valore per chiave certa (__getitem__)
print(u"Il nome del personaggio è {}".format(d["nome"]))

# sfrutto il mini-formato di template per le stringhe
# https://docs.python.org/2.7/library/string.html#formatspec
print(u"Il personaggio è {nome} {cognome} nato nel {classe}".format(**d))

# Recupero di un valore per una chiave opzionale
print(u"'nome' è una chiave che esiste con valore = {}, 'codiceiban' invece non esiste = {}".format(
    d.get('nome'), d.get('codiceiban')))

print(u"Se avessi usato la __getitem__ avrei avuto un KeyError")

# rimozione di una chiave dal dizionario
print(u"Rimuovo il nome dal dizionario con d.pop('nome')")
d.pop('nome')

print(u"'nome' ora non esiste con valore = {}, come 'codiceiban' = {}".format(
    d.get('nome'), d.get('codiceiban')))

print(u"Allora, se non trovi la chiave 'nome' allora dimmi 'Pippo'. Cosa dici?")
print(d.get('nome', 'Pippo'))


Il nome del personaggio è Luca
Il personaggio è Luca Ferroni nato nel 1980
'nome' è una chiave che esiste con valore = Luca, 'codiceiban' invece non esiste = None
Se avessi usato la __getitem__ avrei avuto un KeyError
Rimuovo il nome dal dizionario con d.pop('nome')
'nome' ora non esiste con valore = None, come 'codiceiban' = None
Allora, se non trovi la chiave 'nome' allora dimmi 'Pippo'. Cosa dici?
Pippo

Iterazione nei dizionari

ATTENZIONE: Il contenuto del dizionario non è ordinato! Non c'è alcuna garanzia sull'ordinamento. Per avere garanzia bisogna usare la classe collections.OrderedDict


In [52]:
print("\n***PER ITERARE SU TUTTI GLI ELEMENTI DI UN DIZIONARIO SI USA .iteritems()***\n")

for key, value in d.iteritems():
    print("Alla chiave {} corrisponde il valore {}".format(key,value))

    
print("\n***DIZIONARI E ORDINAMENTO***\n")
data_input = [('a', 1), ('b', 2), ('l', 10), ('c', 3)]
d1 = dict(data_input)
    
import collections

d2_ord = collections.OrderedDict(data_input)

print("input = {}".format(data_input))
print("dizionario non ordinato = {}".format(d1))
print("dizionario ordinato = {}".format(d2_ord))
print("lista di coppie da diz NON ordinato = {}".format(d1.items()))
print("lista di coppie da diz ordinato = {}".format(d2_ord.items()))


***PER ITERARE SU TUTTI GLI ELEMENTI DI UN DIZIONARIO SI USA .iteritems()***

Alla chiave professioni corrisponde il valore ['docente', 'lavoratore autonomo']
Alla chiave cognome corrisponde il valore Ferroni
Alla chiave classe corrisponde il valore 1980

***DIZIONARI E ORDINAMENTO***

input = [('a', 1), ('b', 2), ('l', 10), ('c', 3)]
dizionario non ordinato = {'a': 1, 'c': 3, 'b': 2, 'l': 10}
dizionario ordinato = OrderedDict([('a', 1), ('b', 2), ('l', 10), ('c', 3)])
lista di coppie da diz NON ordinato = [('a', 1), ('c', 3), ('b', 2), ('l', 10)]
lista di coppie da diz ordinato = [('a', 1), ('b', 2), ('l', 10), ('c', 3)]

Caratteristiche del modello dati di Python

Tipi di dato "mutable" e "immutable"

Python Data Model

Ogni oggetto ha:

  • identità -> non cambia mai e si può pensare come l'indirizzo in memoria
  • tipo -> non cambia mai e rappresenta le operazioni che l'oggetto supporta
  • valore -> può cambiare se il tipo è mutable, non se è immutable

Tipi di dato immutable sono:

  • interi
  • stringhe
  • tuple
  • set

Tipi di dato mutable sono:

  • liste
  • dizionari

Tipizzazione forte e dinamica

Da http://stackoverflow.com/questions/11328920/is-python-strongly-typed/11328980#11328980 (v. anche i commenti)

Python is strongly, dynamically typed.

  • Strong typing means that the type of a value doesn't suddenly change. A string containing only digits doesn't magically become a number, as may happen in Perl. Every change of type requires an explicit conversion.
  • Dynamic typing means that runtime objects (values) have a type, as opposed to static typing where variables have a type.

As for example

bob = 1
bob = "bob"

This works because the variable does not have a type; it can name any object. After bob=1, you'll find that type(bob) returns int, but after bob="bob", it returns str. (Note that type is a regular function, so it evaluates its argument, then returns the type of the value.)

Passaggio di parametro per valore o riferimento?

Nessuno dei due! V. https://jeffknupp.com/blog/2012/11/13/is-python-callbyvalue-or-callbyreference-neither/

Call by object, or call by object reference.

Concetto base: in python una variabile è solo un nome per un oggetto (= la tripla id,tipo,valore)

In sostanza il comportamento dipende dal fatto che gli oggetti nominati dalle variabili sono mutable o immutable.

Seguono esempi:


In [8]:
def foo(bar):
    bar.append(42)
    print(bar)
    # >> [42]

answer_list = []
foo(answer_list)
print(answer_list)
# >> [42]


[42]
[42]

In [9]:
def foo(bar):
    bar = 'new value'
    print (bar)
    # >> 'new value'

answer_list = 'old value'
foo(answer_list)
print(answer_list)
# >> 'old value'


new value
old value

Funzioni con parametri posizionali, nominali, e arbitrari

Una funzione si definisce con def <nomefunzione>([parametri]) dove i parametri possono essere:

  • posizionali. Ad es: def hello(who)
  • nominali. Ad es: def hello(who='') o who=None o who='default'
  • entrambi, ma i nominali devono essere messi dopo i posizionali. Ad es: def hello(who, say="How are you?")
  • arbitrari sia posizionali con il simbolo * o nominali con **. Come convenzione si utilizzano i nomi args e kw o kwargs. Ad es: def hello(who, say="How are you?", *args, **kw)

    I simboli * e ** indicano rispettivamente la rappresentazione di una lista come una sequenza di elementi, e di un dizionario come una sequenza di parametri <chiave>=<valore>

Scope delle variabili

http://www.saltycrane.com/blog/2008/01/python-variable-scope-notes/

e ricordatevi che:

for i in [1,2,3]: print(i) print("Sono fuori dal ciclo e posso vedere che i={}".format(i))

Namespace

I namespace in python sono raccoglitori di nomi e posson essere impliciti o espliciti. Sono impliciti lo spazio dei nomi __builtin__ e __main__. Sono espliciti, le classi, gli oggetti, le funzioni e in particolare i moduli.

Posso importare un modulo che mi va a costituire un namespace con import <nomemodulo> e accedere a tutti i simboli top-level inseriti nel modulo come <nomemodulo>.<simbolo>.

L'importazione di simboli singoli all'interno di un modulo in un altro namespace si può fare con from <nomemodulo> import <simbolo>. Quello che non si dovrebbe fare è importare tutti i simboli di un modulo dentro un altro nella forma: from <nomemodulo> import *. Non fatelo, a meno che non strettamente necessario.

Stack delle eccezioni e loro gestione

Lo stack delle eccezioni builtin, ossia già comprese nel linguaggio python sono al link: https://docs.python.org/2/library/exceptions.html#exception-hierarchy

Derivando da essere facilmente se ne possono definire di proprie.

La gestione delle eccezioni avviene in blocchi:

try:
    ...
except [eccezione] [as variabile]:
    ...
else:
    ...
finally:
    ...

Pratica del Duck Typing!

« If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck. »

Segue esempio di composizione del saluto con la gestione delle eccezioni:


In [2]:
# -*- coding: utf-8 -*-

# This is hello_who_3.py
import sys                            # <-- importo un modulo


def compose_hello(who, force=False):   # <-- valore di default
    """
    Get the hello message.
    """ 
    try:                                     # <-- gestione eccezioni `Duck Typing`
        message = "Hello " + who + "!"
    except TypeError:                       # <-- eccezione specifica
    # except TypeError as e:                       # <-- eccezione specifica su parametro e
        print("[WARNING] Il parametro `who` dovrebbe essere una stringa")
        if force:                            # <-- controllo "if"
            message = "Hello {}!".format(who)
        else:
            raise                            # <-- solleva eccezione originale
    except Exception:
        print("Verificatasi eccezione non prevista")
    else:
        print("nessuna eccezione")
    finally:
        print("Bye")
    
    return message


def hello(who='world'):               # <-- valore di default
    print(compose_hello(who))


if __name__ == "__main__":
    hello("mamma")


nessuna eccezione
Bye
Hello mamma!

In [3]:
hello("pippo")


nessuna eccezione
Bye
Hello pippo!

In [4]:
hello(1)


[WARNING] Il parametro `who` dovrebbe essere una stringa
Bye

TypeErrorTraceback (most recent call last)
<ipython-input-4-e35132e5d1c7> in <module>()
----> 1 hello(1)

<ipython-input-2-15533d549f12> in hello(who)
     29 
     30 def hello(who='world'):               # <-- valore di default
---> 31     print(compose_hello(who))
     32 
     33 

<ipython-input-2-15533d549f12> in compose_hello(who, force)
     10     """ 
     11     try:                                     # <-- gestione eccezioni `Duck Typing`
---> 12         message = "Hello " + who + "!"
     13     except TypeError:                       # <-- eccezione specifica
     14     # except TypeError as e:                       # <-- eccezione specifica su parametro e

TypeError: cannot concatenate 'str' and 'int' objects

In [25]:
ret = compose_hello(1, force=True)
print("Ha composto {}".format(ret))


[WARNING] Il parametro `who` dovrebbe essere una stringa
Bye
Ha composto Hello 1!

In [29]:
try:
    hello(1)
except TypeError as e:
    print("{}: {}".format(type(e).__name__, e))
    print("Riprova")


[WARNING] Il parametro `who` dovrebbe essere una stringa
Bye
TypeError: cannot concatenate 'str' and 'int' objects
Riprova

La funzione di Fibonacci


In [ ]:
fib(0) --> 0
fib(1) --> 1
fib(2) --> 1
fib(3) --> 2
fib(n) --> fib(n-1) + fib(n-2)

In [ ]:
import pytest
from myprogram import fib

def test_fib_ok_small():
    assert fib(0) == 0
    assert fib(1) == 1
    assert fib(2) == 1
    assert fib(3) == 2
    
def test_fib_raise_if_string():
    with pytest.raises(TypeError):
        fib("a")

def test_fib_raises_lt_zero():
    with pytest.raises(ValueError):
        fib(-1)